{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 64 单元测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "func sum(a, b int) int {\n",
    "\treturn a + b\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== RUN   TestSum\n",
      "=== RUN   TestSum/positive_numbers\n",
      "=== RUN   TestSum/zero_plus_number\n",
      "=== RUN   TestSum/number_plus_zero\n",
      "=== RUN   TestSum/negative_numbers\n",
      "=== RUN   TestSum/mixed_sign\n",
      "--- PASS: TestSum (0.00s)\n",
      "    --- PASS: TestSum/positive_numbers (0.00s)\n",
      "    --- PASS: TestSum/zero_plus_number (0.00s)\n",
      "    --- PASS: TestSum/number_plus_zero (0.00s)\n",
      "    --- PASS: TestSum/negative_numbers (0.00s)\n",
      "    --- PASS: TestSum/mixed_sign (0.00s)\n",
      "PASS\n"
     ]
    }
   ],
   "source": [
    "func TestSum(t *testing.T) {\n",
    "\ttests := []struct {\n",
    "\t\tname string\n",
    "\t\ta    int\n",
    "\t\tb    int\n",
    "\t\twant int\n",
    "\t}{\n",
    "\t\t{name: \"positive numbers\", a: 2, b: 3, want: 5},\n",
    "\t\t{name: \"zero plus number\", a: 0, b: 7, want: 7},\n",
    "        {name: \"number plus zero\", a: 9, b: 0, want: 9},\n",
    "\t\t{name: \"negative numbers\", a: -4, b: -6, want: -10},\n",
    "\t\t{name: \"mixed sign\", a: -3, b: 8, want: 5},\n",
    "\t}\n",
    "\n",
    "\tfor _, tt := range tests {\n",
    "\t\tt.Run(tt.name, func(t *testing.T) {\n",
    "\t\t\tgot := sum(tt.a, tt.b)\n",
    "\t\t\tif got != tt.want {\n",
    "\t\t\t\tt.Fatalf(\"sum(%d, %d): want %d, got %d\", tt.a, tt.b, tt.want, got)\n",
    "\t\t\t}\n",
    "\t\t})\n",
    "\t}\n",
    "}\n",
    "\n",
    "%test"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `-run`：单测函数需要满足的正则表达式；\n",
    "- `-v`：打印详细测试信息；\n",
    "- `-timeout`：默认 10 分钟超时；\n",
    "- `-count`：函数运行几次。把 `count` 设置为 `1` 可以禁用缓存，否则主函数修改之后测试结果可能不变"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== RUN   TestSum\n",
      "=== RUN   TestSum/positive_numbers\n",
      "=== RUN   TestSum/zero_plus_number\n",
      "=== RUN   TestSum/number_plus_zero\n",
      "=== RUN   TestSum/negative_numbers\n",
      "=== RUN   TestSum/mixed_sign\n",
      "--- PASS: TestSum (0.00s)\n",
      "    --- PASS: TestSum/positive_numbers (0.00s)\n",
      "    --- PASS: TestSum/zero_plus_number (0.00s)\n",
      "    --- PASS: TestSum/number_plus_zero (0.00s)\n",
      "    --- PASS: TestSum/negative_numbers (0.00s)\n",
      "    --- PASS: TestSum/mixed_sign (0.00s)\n",
      "PASS\n"
     ]
    }
   ],
   "source": [
    "%test -test.v -test.run=^TestSum$ -test.count=1 -test.timeout=20m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== RUN   TestCopySliceDestLongerThanSrc\n",
      "--- PASS: TestCopySliceDestLongerThanSrc (0.00s)\n",
      "PASS\n",
      "ok  \txtest/until\t0.004s\n"
     ]
    }
   ],
   "source": [
    "! cd ./64 && go test -v ./until -run=^TestCopySliceDestLongerThanSrc$ -count=1 -timeout=20m"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 65 基准测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "goos: linux\n",
      "goarch: amd64\n",
      "pkg: gonb_83c78129\n",
      "cpu: AMD Ryzen 7 7840HS with Radeon 780M Graphics   \n",
      "BenchmarkSum\n",
      "BenchmarkSum-16    \t1000000000\t         0.2201 ns/op\n",
      "PASS\n"
     ]
    }
   ],
   "source": [
    "func BenchmarkSum(b *testing.B) {\n",
    "\t// 为避免编译器优化掉结果，使用一个累加变量。\n",
    "\tvar sink int\n",
    "\n",
    "\tb.ResetTimer()\n",
    "\tfor i := 0; i < b.N; i++ {\n",
    "\t\tsink = sum(i, i+1)\n",
    "\t}\n",
    "\n",
    "\t// 防止 sink 被编译器优化掉。\n",
    "\t_ = sink\n",
    "}\n",
    "\n",
    "%test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "goos: linux\n",
      "goarch: amd64\n",
      "pkg: gonb_83c78129\n",
      "cpu: AMD Ryzen 7 7840HS with Radeon 780M Graphics   \n",
      "BenchmarkSum-16    \t1000000000\t         0.2204 ns/op\t       0 B/op\t       0 allocs/op\n",
      "PASS\n"
     ]
    }
   ],
   "source": [
    "%test -test.bench=. -test.run=Bechmark -test.count=1 -test.benchmem -test.cpuprofile=./profiles/cpuprofile -test.memprofile=./profiles/memprofile"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `-run=^$`：不运行单元测试，只执行基准测试（匹配不到任何测试函数）。\n",
    "- `-bench=CopySlice$`：仅运行名称以 `CopySlice` 结尾的基准函数。\n",
    "- `-benchtime=3s`：让基准测试持续至少 3 秒（默认 1 秒或 2 秒，取决于 Go 版本）。\n",
    "- `-benchmem`：打印内存信息。\n",
    "- `-count=1`：禁用缓存，确保基准每次重新编译运行。\n",
    "- `-cpuprofile`：生成 CPU 性能剖析文件，可用 `go tool pprof` 分析。\n",
    "- `-memprofile`：生成内存剖析文件。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "goos: linux\n",
      "goarch: amd64\n",
      "pkg: xtest/until\n",
      "cpu: AMD Ryzen 7 7840HS with Radeon 780M Graphics   \n",
      "BenchmarkCopySlice\n",
      "BenchmarkCopySlice/N=0\n",
      "BenchmarkCopySlice/N=0-16         \t1000000000\t         0.4465 ns/op\t       0 B/op\t       0 allocs/op\n",
      "BenchmarkCopySlice/N=1\n",
      "BenchmarkCopySlice/N=1-16         \t1000000000\t         0.8862 ns/op\t       0 B/op\t       0 allocs/op\n",
      "BenchmarkCopySlice/N=8\n",
      "BenchmarkCopySlice/N=8-16         \t1000000000\t         2.444 ns/op\t       0 B/op\t       0 allocs/op\n",
      "BenchmarkCopySlice/N=64\n",
      "BenchmarkCopySlice/N=64-16        \t234478664\t        15.29 ns/op\t       0 B/op\t       0 allocs/op\n",
      "BenchmarkCopySlice/N=1024\n",
      "BenchmarkCopySlice/N=1024-16      \t15479402\t       235.1 ns/op\t       0 B/op\t       0 allocs/op\n",
      "BenchmarkCopySlice/N=32768\n",
      "BenchmarkCopySlice/N=32768-16     \t  463809\t      7495 ns/op\t       1 B/op\t       0 allocs/op\n",
      "PASS\n",
      "ok  \txtest/until\t16.908s\n"
     ]
    }
   ],
   "source": [
    "! cd ./65 && go test -v ./until -run=^$ -bench=CopySlice$ -benchmem -benchtime=3s -count=1 -cpuprofile=./cpuprofile -memprofile=./memprofile"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 直接输入 `top` 查看热点函数。\n",
    "- 使用 `top5`/`top10` 限定数量。\n",
    "- `list <FuncName>`：查看某个函数的源码及耗时标记。\n",
    "- `web`：以图形化形式展示（需安装 Graphviz）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(pprof) "
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "File: until.test\n",
      "Build ID: 705d4a7a1f1e5215456a9839d6a264f65c38c56f\n",
      "Type: cpu\n",
      "Time: 2025-09-29 21:54:57 CST\n",
      "Duration: 16.87s, Total samples = 16.79s (99.54%)\n",
      "Entering interactive mode (type \"help\" for commands, \"o\" for options)\n",
      "go tool pprof: signal: interrupt\n",
      "exit status 1\n"
     ]
    }
   ],
   "source": [
    "! cd ./65 && go tool pprof cpuprofile"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 建议提供二进制以获得更完整符号"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "! cd ./65/ && go test -c -o until.test ./until"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "! cd ./65/ && go tool pprof until.test cpuprofile // 需要交互"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 本地 Web 界面（包含火焰图、调用图等）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Fetching profile over HTTP from http://localhost:8080/debug/pprof/profile\n",
      "//: read //: is a directory\n",
      "然后访问: stat 然后访问: no such file or directory\n",
      "http://localhost:8080: Get \"http://localhost:8080/debug/pprof/profile\": dial tcp 127.0.0.1:8080: connect: connection refused\n",
      "Fetched 1 source profiles out of 4\n",
      "Serving web UI on http://localhost:8080\n",
      "Couldn't find a suitable web browser!\n",
      "\n",
      "Set the BROWSER environment variable to your desired browser.\n",
      "\n",
      "go tool pprof: signal: interrupt\n",
      "exit status 1\n"
     ]
    }
   ],
   "source": [
    "! cd ./65 && go tool pprof -http=:8080 until.test cpuprofile // 然后访问 http://localhost:8080"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![65.1](./65/images/65.1.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 导出调用图"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "! cd ./65 && go tool pprof -png until.test cpuprofile > callgraph.png"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 66 go项目组织方式"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 项目组织方式"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 顶层约定\n",
    "\n",
    "```shell\n",
    "your-project/\n",
    "├── go.mod\n",
    "├── go.sum\n",
    "├── go.work          # 多模块仓库时使用\n",
    "├── README.md\n",
    "├── LICENSE\n",
    "├── Makefile / Taskfile.yml\n",
    "├── docs/\n",
    "├── cmd/\n",
    "├── internal/\n",
    "├── pkg/             # 供外部复用的公共包（仅当确有需求）\n",
    "├── api/             # API 定义（OpenAPI, gRPC proto, GraphQL schema 等）\n",
    "├── configs/\n",
    "├── deployments/     # IaC 模板、Helm、Kustomize、Terraform 等\n",
    "├── scripts/         # 辅助脚本，建议 Shell + Go-based CLI\n",
    "├── test/            # 集成测试、e2e 测试\n",
    "└── tools.go / toolchain.go\n",
    "```\n",
    "\n",
    "### 顶层文件说明\n",
    "\n",
    "- **go.mod**：主模块定义（Go ≥1.21 默认启用 workspace 语义；单模块项目直接使用）。\n",
    "- **go.work**：处于 monorepo、需要多个模块协作或需要引用未发布模块时使用（见 §3）。\n",
    "- **Makefile / Taskfile.yml**：一站式封装 `go test`, `go vet`, `staticcheck`, `docker build` 等命令。\n",
    "- **tools.go**：通过空导入管理依赖的工具（`//go:build tools`），确保 CI/CD 可复现实验环境。\n",
    "- **docs/**：架构设计、ADR（Architecture Decision Record）、API 文档可整理在此。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. 包结构\n",
    "\n",
    "### 2.1 `cmd/` —— 可执行入口\n",
    "\n",
    "\n",
    "\n",
    "```ebnf\n",
    "cmd/\n",
    "  yourservice/\n",
    "    main.go      # 业务注入 & CLI\n",
    "  migrator/\n",
    "    main.go      # 数据库迁移工具\n",
    "  cron/\n",
    "    main.go      # 计划任务\n",
    "```\n",
    "\n",
    "- 每个子目录对应一个独立的可执行文件。\n",
    "- main 函数保持最小化，主要负责解析配置、依赖注入、启动服务。\n",
    "\n",
    "### 2.2 `internal/` —— 私有逻辑（推荐）\n",
    "\n",
    "\n",
    "\n",
    "```ebnf\n",
    "internal/\n",
    "  app/           # 应用服务层（聚合业务用例）\n",
    "  domain/        # 领域模型、接口（DDD 风格）\n",
    "  infra/         # 数据库、缓存、消息队列实现\n",
    "  transport/     # HTTP/gRPC/GraphQL 适配层\n",
    "  config/        # 配置加载（viper/envconfig/koanf 等）\n",
    "  logging/\n",
    "  metrics/\n",
    "```\n",
    "\n",
    "- `internal` 可见性限制保证模块化、降低 API 泄露风险。\n",
    "- 可按 DDD、分层、Clean Architecture 进行拆分；保持依赖方向从外到内。\n",
    "\n",
    "### 2.3 `pkg/` —— 可复用公共库（可选）\n",
    "\n",
    "- 只在确实需要被其他项目导入时使用，避免成为“垃圾箱”。\n",
    "- 示例：共享 SDK、通用的 JWT 库、仪表封装等。\n",
    "- 若项目内部使用，优先放到 `internal`。\n",
    "\n",
    "### 2.4 其它目录\n",
    "\n",
    "- **api/**：OpenAPI、protobuf、GraphQL schema 等文件，配合 `buf`、`oapi-codegen` 等工具生成代码。\n",
    "- **configs/**：默认配置、样例 YAML/TOML/JSON；生产环境配置建议通过环境变量或配置中心注入。\n",
    "- **deployments/**：Helm chart、Kustomize、Terraform、Pulumi 等基础设施文件。\n",
    "- **scripts/**：初始化数据、CI 辅助脚本（尽量使用 Go 或 Bash；确保跨平台性）。\n",
    "- **test/**：集成测试、测试数据、mock server；E2E 可在此使用 `docker compose` 或 `testcontainers-go`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. 多模块与 Workspace（Go 1.22+ 建议）\n",
    "\n",
    "### 3.1 单仓库多模块\n",
    "\n",
    "适用于微服务、多 SDK、多语言绑定等场景。\n",
    "\n",
    "\n",
    "```maxima\n",
    "go.work\n",
    "module main/go.mod\n",
    "module sdk/go.mod\n",
    "module tools/go.mod\n",
    "```\n",
    "\n",
    "`go.work` 示例：\n",
    "\n",
    "```go\n",
    "go 1.22\n",
    "\n",
    "use (\n",
    "    ./main\n",
    "    ./sdk\n",
    "    ./tools\n",
    ")\n",
    "\n",
    "replace example.com/shared => ./shared\n",
    "```\n",
    "\n",
    "- 优点：各模块独立版本、依赖清晰。\n",
    "- 缺点：需要同步管理 `go.work`，CI 使用 `go work sync` 保持一致。\n",
    "\n",
    "### 3.2 单模块 Workspace\n",
    "\n",
    "- 中小项目或单体应用，多数情况下单 `go.mod` 已足够。\n",
    "- 利用 Go 1.22 新引入的默认 workspace 模式纳入工具链，不再需要 `GO111MODULE` 等环境变量。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. 配置与环境管理\n",
    "\n",
    "- **配置封装**：使用 `envconfig`, `koanf`, `caarlos0/env` 等库。建议定义结构体并集中管理默认值、校验逻辑。\n",
    "- **十二要素**：将敏感信息放环境变量或秘密管理服务（Vault/Secrets Manager）。\n",
    "- **环境划分**：`config/` 仅放样例；真正的 dev/staging/prod 配置由 CI/CD 或容器平台管理。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. 测试与质量保障\n",
    "\n",
    "- **单元测试**：与被测文件同目录，`*_test.go`。\n",
    "- **基准测试**：`Benchmark*`，配合 `go test -bench`、`pprof`。\n",
    "- **集成测试**：放在 `test/` 下或使用 build tag `//go:build integration`。\n",
    "- **工具链**：\n",
    "  - `golangci-lint`、`staticcheck`：静态分析。\n",
    "  - `go test -race`：数据竞争检测。\n",
    "  - `gosec`, `govulncheck`：安全扫描。\n",
    "  - `buf`：统一管理 proto。\n",
    "- **CI/CD**：GitHub Actions/GitLab CI/Drone 等，确保每个分支都执行测试与 lint。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. 依赖与版本管理\n",
    "\n",
    "- 使用 `go mod tidy` 保持依赖精简。\n",
    "- `go.mod` 中显式写明 `go 1.22`（或最新稳定版），并设置 `toolchain`（Go 1.21+ 支持）：\n",
    "\n",
    "```shell\n",
    "go 1.22\n",
    "toolchain go1.23.1\n",
    "```\n",
    "\n",
    "这样在 CI/CD 中自动使用指定 Go 版本。\n",
    "\n",
    "- 对外部工具依赖维护 `tools.go`：\n",
    "\n",
    "```go\n",
    "//go:build tools\n",
    "  \n",
    "package tools\n",
    "  \n",
    "import (\n",
    "  _ \"github.com/bufbuild/buf/cmd/buf\"\n",
    "  _ \"golang.org/x/tools/cmd/stringer\"\n",
    ")\n",
    "```\n",
    "\n",
    "并通过 \n",
    "```shell\n",
    "go install\n",
    "```\n",
    "安装。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. 代码组织模式\n",
    "\n",
    "### 7.1 分层架构（Layered / Clean Architecture）\n",
    "\n",
    "```ebnf\n",
    "internal/\n",
    "  domain/\n",
    "  application/\n",
    "  infrastructure/\n",
    "  interfaces/\n",
    "```\n",
    "\n",
    "- 领域实体与接口位于 `domain/`。\n",
    "- `application/` 实现用例。\n",
    "- `infrastructure/` 处理数据库、外部服务。\n",
    "- `interfaces/`（或 `transport/`）负责 HTTP/gRPC。\n",
    "\n",
    "### 7.2 DDD + Module Pattern\n",
    "\n",
    "- 按业务上下文划分包：`internal/orders`, `internal/payments`。\n",
    "- 每个上下文内包含 domain、repository、service、handler 子包。\n",
    "\n",
    "### 7.3 事件驱动 / 微服务\n",
    "\n",
    "- 在 `internal/transport` 下区分 HTTP、gRPC、消息总线处理（Kafka/NATS/RabbitMQ）。\n",
    "- 抽象消息队列接口，具体实现放在 `infra`。\n",
    "- 事件定义放置在 `api/events` 或专属模块。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. 现代特性与建议（2024/2025）\n",
    "\n",
    "- **泛型（Go 1.18+）**：针对集合、缓存、SQL 扫描等工具包，可适度引入泛型以简化代码。\n",
    "- **Context-aware Logging/Metrics**：统一 logging 接口（zerolog, zap, slog），结合 `otel` 进行链路追踪。\n",
    "- **Observability**：`internal/observability` 集成 OpenTelemetry，提供 tracer/meter/router 装饰器。\n",
    "- **Configurable Module Path**：对外发布库时使用语义化版本 `vX`，保持 API 向后兼容。\n",
    "- **Safety build tags**：为特定环境（如 `linux`, `integration`）提供 build tag，确保本地/CI 行为一致。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. 示例项目骨架\n",
    "\n",
    "```text\n",
    "your-project/\n",
    "├── go.mod\n",
    "├── go.sum\n",
    "├── go.work\n",
    "├── README.md\n",
    "├── Makefile\n",
    "├── docs/\n",
    "│   └── architecture.md\n",
    "├── cmd/\n",
    "│   ├── api/\n",
    "│   │   └── main.go\n",
    "│   └── worker/\n",
    "│       └── main.go\n",
    "├── internal/\n",
    "│   ├── app/\n",
    "│   │   ├── service.go\n",
    "│   │   └── service_test.go\n",
    "│   ├── auth/\n",
    "│   ├── orders/\n",
    "│   ├── infra/\n",
    "│   │   ├── db/\n",
    "│   │   └── cache/\n",
    "│   ├── transport/\n",
    "│   │   ├── http/\n",
    "│   │   └── grpc/\n",
    "│   └── observability/\n",
    "├── api/\n",
    "│   ├── openapi/\n",
    "│   │   └── swagger.yaml\n",
    "│   └── proto/\n",
    "│       ├── orders.proto\n",
    "│       └── buf.yaml\n",
    "├── configs/\n",
    "│   ├── config.example.yaml\n",
    "│   └── app.local.env\n",
    "├── deployments/\n",
    "│   ├── docker-compose.yml\n",
    "│   ├── helm/\n",
    "│   └── terraform/\n",
    "├── scripts/\n",
    "│   ├── run_local.sh\n",
    "│   └── migrate.sh\n",
    "├── test/\n",
    "│   └── integration/\n",
    "│       └── orders_test.go\n",
    "└── tools.go\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 10. 迁移与演进建议\n",
    "\n",
    "1. **从 GOPATH 向 Modules 迁移**：Go 1.22+ 默认启用 Modules；旧项目需确保 `go.mod` 完整。\n",
    "2. **逐步引入 Workspace**：在 monorepo 中使用 `go work use ./path` 管理子模块，减少 `replace` 维护成本。\n",
    "3. **持续重构目录**：随着业务演进，将通用逻辑抽至 `internal/shared` 或独立模块，减少重复。\n",
    "4. **自动化验证**：在 CI 中执行 `go fmt`, `go vet`, `golangci-lint`, `go test ./...`, `go test -race`, `go test -bench`, `govulncheck`。\n",
    "5. **强化文档化**：通过 ADR 或 `docs/` 目录记录结构调整、技术决策，便于 2025 年后新成员 onboarding。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 开源项目"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. 官方/官方团队维护的示例\n",
    "\n",
    "| 项目                                                         | 维护方                    | 亮点                                                         |\n",
    "| ------------------------------------------------------------ | ------------------------- | ------------------------------------------------------------ |\n",
    "| [`golang/example`](https://github.com/golang/example)        | Go 团队                   | 各类官方示例程序与教程配套代码。结构简洁，适合入门理解模块组织与工具链使用。 |\n",
    "| [`golang/tools`](https://github.com/golang/tools)            | Go 团队                   | Go 官方工具链与实验性工具，采用多模块布局、`internal` 隔离及 Go Workspace。可观察大型工具仓库的组织方式。 |\n",
    "| [`deepmap/oapi-codegen`](https://github.com/deepmap/oapi-codegen) | Go 团队成员参与维护       | 包含 `cmd/`, `internal/`, `examples/` 等目录，展示 CLI + Library 双角色项目如何布局。 |\n",
    "| [`protoyaml/buf`](https://github.com/bufbuild/buf/tree/main) | 前 Google Go 团队成员创立 | 多模块 + Bazel/Buf 集成，结合 Go 模块与工具的现代组织方式。  |\n",
    "| [`go.dev` 教程示例](https://go.dev/doc/tutorial)             | Go 官方文档               | 虽然规模不大，但教程展示了 Go Modules、测试、Workspace 的标准使用流程。 |\n",
    "\n",
    "> **提示**：`golang-standards/project-layout` 虽然名字带 “standards”，但并非官方项目，仅是社区总结；可当作灵感，不要误认为官方强制规范。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. 优秀社区项目（现代结构）\n",
    "\n",
    "| 项目                                                         | 结构特征                                                     | 适用场景                                                     |\n",
    "| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| [`99designs/gqlgen`](https://github.com/99designs/gqlgen)    | `cmd/`、`internal/`, `pkg/`, `examples/`，配合 `tools.go` 管理生成器 | GraphQL 框架，展示如何维护代码生成工具与多模块。             |\n",
    "| [`uber-go/fx`](https://github.com/uber-go/fx)                | 清晰的 `module` 概念、`app/`, `fxevent/` 等包划分，单模块内层次分明 | 依赖注入框架，体现大型库项目的包拆分方式。                   |\n",
    "| [`go-kratos/kratos`](https://github.com/go-kratos/kratos)    | `cmd/`, `internal/`, `configs/`, `docs/`, `deployments/`, `interfaces/` 等丰富目录；支持 CLI 脚手架 | 微服务框架，展示现代 Go 服务端项目典型结构（包含脚手架生成代码）。 |\n",
    "| [`bufbuild/connect-go`](https://github.com/bufbuild/connect-go) | `internal/` + `cmd/` + `examples/`，配合 Bazel 等工具链      | gRPC/Connect 生态库，展示接口协议层与核心逻辑分层。          |\n",
    "| [`cockroachdb/cockroach`](https://github.com/cockroachdb/cockroach) | 超大规模 monorepo，Bazel + Go Modules 混合；`pkg/` + `internal/` 分层 | 分布式数据库；示例如何在巨型项目中组合 Go 与其他语言/工具。  |\n",
    "| [`temporalio/sdk-go`](https://github.com/temporalio/sdk-go)  | 包内分层、`internal/testing`, `tests/`，多模块组织           | 有全面测试、示例和工具链集成，适合学习 SDK 项目的布局。      |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. 脚手架/模板类项目\n",
    "\n",
    "| 项目                                                         | 说明                                                         |\n",
    "| ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| [`go-chi/chi` 官方示例](https://github.com/go-chi/chi/tree/master/_examples) | 提供 REST API 项目的基础结构，可结合 `internal`/`pkg` 自定义。 |\n",
    "| [`golang-standards/project-layout`](https://github.com/golang-standards/project-layout) | 社区最常引用的参考模板，涵盖 `cmd/`, `pkg/`, `internal/`, `configs/`, `deployments/` 等；非官方，但目录覆盖面广。 |\n",
    "| [`sagikazarmark/modern-go-application`](https://github.com/sagikazarmark/modern-go-application) | 专注现代 Go 应用启动流程、配置、DI、Observability；演示如何组合常见库（Fx/Wire、Viper、Zap、Prometheus）。 |\n",
    "| [`kubebuilder` 项目模板](https://github.com/kubernetes-sigs/kubebuilder) | 虽主要针对 Kubernetes Operator，模板中 `cmd/`, `pkg/`, `api/`、`config/` 等组织值得借鉴。 |\n",
    "| [`looplab/fsm` 示例项目](https://github.com/looplab/eventhorizon/tree/master/examples) | DDD + CQRS + Event Sourcing 实践结构，适合需要复杂领域建模的项目参考。 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. 观察点与学习方式\n",
    "\n",
    "1. **Compare + Borrow**\n",
    "    克隆仓库后重点查看：\n",
    "   - `go.mod`、`go.work` 如何管理多模块。\n",
    "   - `cmd/` 内 main 入口如何进行依赖注入。\n",
    "   - `internal/`、`pkg/` 的分层策略。\n",
    "   - 测试与基准测试目录布局（`*_test.go` 位置、`test/` 目录结构）。\n",
    "   - `Makefile`/`Taskfile` 中定义的开发流程命令。\n",
    "   - `docs/` 和 `deployments/` 对运维/部署的支持方式。\n",
    "2. **工具链集成**\n",
    "    注意是否使用：\n",
    "   - `golangci-lint`, `staticcheck`, `govulncheck`。\n",
    "   - `buf`/`oapi-codegen` 等代码生成工具，并通过 `tools.go` 固化版本。\n",
    "   - `Dockerfile`, `docker-compose`, `Helm`, `Terraform` 等部署文件的组织。\n",
    "3. **Workspace & Monorepo**\n",
    "    2024/2025 越来越多项目依赖 `go.work`。可以参考 `buf`、`cockroachdb` 等仓库观摩 `go work use`、`replace` 的组合方式、CI 中的 `go work sync` 使用方法。\n",
    "4. **文档与 ADR**\n",
    "    观察 `docs/` 中是否有 Architecture Decision Records 或设计说明，了解项目如何记录演进过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 67 管理第三方依赖库"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. **统一使用 Go Modules**\n",
    "   - 保持 `go 1.xx` 与 `toolchain` 字段最新，确保可重复的构建环境。\n",
    "   - 所有依赖通过 `go.mod`/`go.sum` 管理，不再依赖 GOPATH。\n",
    "2. **遵循最小版本选择（Minimal Version Selection, MVS）**\n",
    "   - Go 会在构建时自动选择满足约束的最低兼容版本；显式 `require` 只写业务真正依赖的模块。\n",
    "   - 遇到安全或兼容问题，可通过 `replace` 临时拉升版本，但应尽快移除并回归上游修复。\n",
    "3. **维护干净的 go.mod / go.sum**\n",
    "   - 频繁执行 `go mod tidy`，移除未使用的依赖。\n",
    "   - 结合 CI/CD 设置 `go mod tidy && git diff --exit-code go.mod go.sum`，防止脏依赖入库。\n",
    "4. **固定可执行工具依赖**\n",
    "   - 使用 `tools.go`（`//go:build tools`）空导入方式锁定代码生成器、Lint 工具等版本，避免 CI 与本地不一致。\n",
    "   - 执行 `go install ./...` 时带 `GOBIN` 指向本地工具目录。\n",
    "5. **优先使用官方代理与校验**\n",
    "   - 默认 `GOPROXY=https://proxy.golang.org,direct`、`GOSUMDB=sum.golang.org`，确保依赖内容可溯源、防篡改。\n",
    "   - 内网或私有模块可通过企业代理链，必要时配置 `GONOSUMDB`, `GOPRIVATE`。\n",
    "6. **定期安全扫描与升级**\n",
    "   - 在 CI 中集成 `govulncheck`、`golangci-lint`, `gosec` 等工具，及时识别依赖漏洞。\n",
    "   - 遵守语义化版本（SemVer），更新时关注 `CHANGELOG`；对重大更新先在分支或环境中验证回归。\n",
    "7. **保持 Vendor 可选**\n",
    "   - 在需要离线构建或审计时使用 `go mod vendor`，但 vendor 目录不与主干混用；确保镜像/制品中的依赖与 go.sum 一致。\n",
    "   - 若启用 vendor 构建，CI 中运行 `go mod vendor && git diff --exit-code vendor/` 保证同步。\n",
    "8. **记录依赖决策**\n",
    "   - 通过 ADR/文档记录引入或替换依赖的原因、评估结果与回退方案。\n",
    "   - 对关键三方库设立监控（订阅 release/安全公告），预防性计划升级窗口。\n",
    "9. **自动化审计**\n",
    "   - 利用 `Dependabot`、`Renovate` 或自建脚本自动检测更新；引入后需人工评估并运行完整测试套件。\n",
    "   - 对多模块/Workspace 项目，确保 `go work sync` 后依赖树一致。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "! cd ./67 && go env -w GOPROXY=https://goproxy.cn,direct"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "! cd ./67 && go get github.com/bytedance/sonic@v1.14.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".\n",
      "├── go.mod\n",
      "├── go.sum\n",
      "└── main.go\n",
      "\n",
      "0 directories, 3 files\n"
     ]
    }
   ],
   "source": [
    "! cd ./67 && tree"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "! cd ./67 && cat "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "module demo\n",
      "\n",
      "go 1.24.3\n",
      "\n",
      "require (\n",
      "\tgithub.com/bytedance/gopkg v0.1.3 // indirect\n",
      "\tgithub.com/bytedance/sonic v1.14.1 // indirect\n",
      "\tgithub.com/bytedance/sonic/loader v0.3.0 // indirect\n",
      "\tgithub.com/cloudwego/base64x v0.1.6 // indirect\n",
      "\tgithub.com/klauspost/cpuid/v2 v2.2.9 // indirect\n",
      "\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n",
      "\tgolang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect\n",
      "\tgolang.org/x/sys v0.22.0 // indirect\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "! cd ./67 && cat go.mod"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=\n",
      "github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=\n",
      "github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=\n",
      "github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=\n",
      "github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=\n",
      "github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=\n",
      "github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=\n",
      "github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=\n",
      "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\n",
      "github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\n",
      "github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=\n",
      "github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=\n",
      "github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\n",
      "github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\n",
      "github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\n",
      "github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\n",
      "github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\n",
      "github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\n",
      "github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\n",
      "github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\n",
      "github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\n",
      "golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=\n",
      "golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=\n",
      "golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=\n",
      "golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\n",
      "gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\n",
      "gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n",
      "gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
     ]
    }
   ],
   "source": [
    "! cd ./67 && cat go.sum"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "JSON: {\"id\":42,\"name\":\"Alice\",\"tags\":[\"golang\",\"performance\"],\"email\":\"alice@example.com\"}\n",
      "User struct: {ID:42 Name:Alice Tags:[golang performance] Email:alice@example.com}\n",
      "Generic: map[string]interface {}{\"email\":\"alice@example.com\", \"id\":42, \"name\":\"Alice\", \"tags\":[]interface {}{\"golang\", \"performance\"}}\n"
     ]
    }
   ],
   "source": [
    "! cd ./67 && go run ."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "go: downloading github.com/davecgh/go-spew v1.1.1\n",
      "go: downloading github.com/stretchr/testify v1.10.0\n",
      "go: downloading github.com/pmezard/go-difflib v1.0.0\n"
     ]
    }
   ],
   "source": [
    "! cd ./67 && go mod tidy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 68 跨文件函数调不通？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 Go 项目中，函数定义在一个文件、调用写在另一个文件却始终报错或无法调用，通常是以下几个方面出了问题：\n",
    "\n",
    "1. **包名不一致**\n",
    "   - Go 以“包”为编译单元，同一目录下的所有文件必须使用同一个 `package` 声明。\n",
    "   - 若某个文件误写成 `package main`，另一个文件写成 `package utils`，即便在同一目录，编译器也会认为它们属于不同包，自然互相不可见。\n",
    "2. **函数未导出（大小写问题）**\n",
    "   - 在 Go 中，标识符首字母大写才对包外可见。例如 `func helper()` 只能在同包内调用；`func Helper()` 才能被其他包的文件访问。\n",
    "   - 若跨目录或跨包调用，注意把函数首字母改为大写，并在调用处正确引入包。\n",
    "3. **文件不在同一编译单元**\n",
    "   - 不同包的文件即便位于同一个模块内，也需要通过 `import` 语句引入。\n",
    "   - 引入时需使用完整模块路径，且避免循环依赖（A 导入 B，B 又导入 A，会导致编译失败）。\n",
    "4. **构建标签或平台差异**\n",
    "   - 某些文件带有 `//go:build` 或 `// +build` 条件，导致编译时被排除。\n",
    "   - 若调用方文件被编译进来，而被调用函数所在文件因标签不匹配而被忽略，就会出现“找不到函数”的现象。\n",
    "5. **函数签名或返回值不一致**\n",
    "   - 常见于复制代码或重构时，修改了参数/返回类型，而调用方仍按旧签名调用，导致编译错误。\n",
    "   - 记得同步更新所有调用点，或通过 IDE/编译器错误信息定位。\n",
    "6. **命名冲突或多重定义**\n",
    "   - 若同包内出现同名函数（可能来自自动生成代码或手动粘贴），编译会报重复定义。\n",
    "   - 还有可能同名文件存在于不同包，导入时被误识别为别的函数。\n",
    "7. **构建缓存或模块未同步**\n",
    "   - 使用 Go Modules 时，`go build`/`go test` 会基于当前 `go.mod` 与缓存构建。\n",
    "   - 若修改了文件但构建系统没刷新缓存，可尝试 `go clean -cache` 或 `go mod tidy` 确认依赖同步"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "调试建议\n",
    "\n",
    "- **查看编译错误信息**：Go 编译器通常会给出精确提示，例如“undefined: FooBar”或“cannot refer to unexported name”。\n",
    "- **确保包路径正确**：在跨包调用时，路径必须以模块名开头，且与目录结构一致。\n",
    "- **使用 IDE/Go 命令**：`go list -deps ./...`、`go build ./...` 可以帮助快速发现缺失文件或包名冲突。\n",
    "- **检查 build tag**：确认没有意外的 `//go:build` 条件过滤掉目标文件。\n",
    "- **代码生成工具**：若函数来自生成文件，确保运行了生成命令（例如 `go generate`、`protoc` 等）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 69 可见性问题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Go 通过 **包(package)** 划定可见性边界。标识符是否能被包外使用，取决于它的首字母大小写以及所在包的结构。了解这些规则有助于处理“函数/变量调不通”“字段访问不到”等常见问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "常见陷阱与提示\n",
    "\n",
    "1. **包名写错**：同目录不同包导致无法访问；正确做法是保持包名一致或通过导出+`import`。\n",
    "2. **循环依赖**：包 A 导出给包 B 使用，同时 B 又反向 import A，Go 会报循环依赖错误。\n",
    "3. **测试文件**：`*_test.go` 文件仍遵守可见性规则。若使用 `package foo_test`，则只能访问导出标识符。\n",
    "4. **反射访问私有字段**：可利用 `unsafe` 或 `reflect` 对未导出字段操作，但不推荐在业务代码中使用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "快速排查可见性问题\n",
    "\n",
    "- **阅读编译器错误**：Go 编译器会提示 `undefined` 或 `cannot refer to unexported name`。\n",
    "- **检查首字母大小写**：判断目标标识符是否导出。\n",
    "- **确认项目结构**：确保包路径、`import` 语句正确无误。\n",
    "- **使用 IDE 提示**：现代 IDE 会在你尝试访问未导出标识符时提前报错。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "首字母大小写决定导出与否\n",
    "- **首字母大写**：标识符对包外可见（导出，exported）。\n",
    "- **首字母小写**：仅在同包内可见（未导出，unexported）。\n",
    "- 适用于：函数、变量、常量、类型、结构体字段、方法名等。\n",
    "\n",
    "同包内无需导出\n",
    "- 同一包内的文件无论是否在同一个目录（前提是使用 Go Modules 正常导入），只要 `package` 名一致，就可访问首字母小写的标识符。\n",
    "\n",
    "结构体字段与方法可见性\n",
    "- 结构体字段和方法同样遵循大小写规则。\n",
    "\n",
    "嵌套包与目录结构\n",
    "- Go 模块（module）下的子目录若声明不同 `package`，就形成新的包，跨包访问必须导出。\n",
    "- 包名不必等于目录名，但建议保持一致以减小混淆。\n",
    "\n",
    "接口实现的可见性\n",
    "- 方法名大小写同样决定是否导出。\n",
    "- 接口类型本身和方法签名必须在使用点可见。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 70 单测文件放哪儿比较好"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- **默认**：把单测文件放在被测包的同一目录，命名为 `xxx_test.go`。\n",
    "- **根据需要选择包名**：白盒测试用同包名，黑盒测试用 `package foo_test`。\n",
    "- **利用 `go test` 自动发现**：保持惯例可减少维护成本，提高团队一致性。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 Go 中，单元测试文件通常放在**与被测代码同一目录下**，并以 `_test.go` 结尾。这是 Go 官方推荐的文件组织方式，能够让 `go test` 命令自动发现并运行测试。下面是一些实践要点和可选策略："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 默认做法：同目录 + `_test.go`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```go\n",
    "pkg/\n",
    "├── math.go\n",
    "└── math_test.go   // 与 math.go 同目录\n",
    "```\n",
    "\n",
    "- 运行 `go test ./pkg` 或 `go test ./...` 时，测试会被自动发现。\n",
    "- 测试文件只在测试构建中编译，不会影响正常的 `go build`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 同包测试 vs. 外部测试"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 同包测试（package foo）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 测试文件与被测代码使用相同包名。\n",
    "- 可以访问未导出的函数/变量（便于白盒测试）。\n",
    "- 适合大多数单元测试场景。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```go\n",
    "package math // math.go\n",
    "\n",
    "func sum(a, b int) int {\n",
    "\treturn a + b\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```go\n",
    "package math // math_test.go\n",
    "\n",
    "import \"testing\"\n",
    "\n",
    "func TestSum(t *testing.T) {\n",
    "\tif sum(2, 3) != 5 {\n",
    "\t\tt.Fatal(\"expected 5\")\n",
    "\t}\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 外部测试（package foo_test）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 测试文件以 `_test` 结尾的包名。例如 `package math_test`。\n",
    "- 只能访问导出的 API（黑盒测试）。\n",
    "- 有助于验证包的外部使用方式，避免依赖内部实现细节。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```go\n",
    "package math_test\n",
    "\n",
    "import (\n",
    "\t\"testing\"\n",
    "\n",
    "\t\"yourmodule/math\"\n",
    ")\n",
    "\n",
    "func TestAdd(t *testing.T) {\n",
    "\tif math.Add(2, 3) != 5 {\n",
    "\t\tt.Fatal(\"expected 5\")\n",
    "\t}\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 为什么不推荐单独测试目录？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Go 的 `go test` 机制即依赖 `_test.go` 与包目录的约定。如果把测试放在独立的 `test/` 或 `tests/` 目录，会带来导入路径混乱、访问性受限、命令行不一致等问题。\n",
    "- 若确实需要跨包测试（例如端到端场景），可以在根目录创建单独的包，但依旧使用 `_test.go` 文件形式并通过 `go test ./...` 集中运行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 71 单测覆盖率"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "常见实践建议\n",
    "\n",
    "1. **分层测试**：核心逻辑保持高覆盖率，外围接口可适度降低。\n",
    "2. **关注语句质量**：覆盖率高不等于测试质量高，仍需保证断言合理。\n",
    "3. **结合基准测试**：`go test -bench` 与覆盖率互不冲突，可分开运行。\n",
    "4. **CI 集成**：将 `go test -coverprofile` 写入流水线，搭配 Artifact 或 Coverage 服务（如 Codecov）自动化展示。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "基本命令\n",
    "- `-cover` 选项会在测试运行结束后打印每个包的覆盖率百分比。\n",
    "- `./...` 表示递归遍历当前模块中的所有包。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\txtest\t\tok  \txtest/until\t0.004s\tcoverage: 100.0% of statements\n"
     ]
    }
   ],
   "source": [
    "! cd ./71 && go test -cover ./..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "生成覆盖率报告文件\n",
    "- 将覆盖率数据写入 `coverage.out` 文件。\n",
    "- 可用于进一步分析或生成图形化报告。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\txtest\t\tok  \txtest/until\t0.005s\tcoverage: 100.0% of statements\n"
     ]
    }
   ],
   "source": [
    "! cd ./71 && go test -coverprofile=coverage.out ./..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "查看覆盖率详情\n",
    "- 文本模式（按函数/语句统计）\n",
    "- HTML 可视化（语句级高亮）\n",
    "  - 会在浏览器中打开 HTML 报告，绿/红色标记表示被覆盖与未覆盖的代码行。\n",
    "  - 在 CI 环境中可将 HTML 报告保存为构建产物供下载"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "xtest/main.go:3:\t\tmain\t\t0.0%\n",
      "xtest/until/copy_slice.go:4:\tCopySlice\t100.0%\n",
      "total:\t\t\t\t(statements)\t100.0%\n"
     ]
    }
   ],
   "source": [
    "! cd ./71 && go tool cover -func=coverage.out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "! cd ./71 && go tool cover -html=coverage.out -o ./coverage.html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "按包查看覆盖率\n",
    "- 或编写脚本，为每个包生成专属 coverprofile 文件进行汇总。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\txtest\t\tok  \txtest/until\t(cached)\tcoverage: 100.0% of statements\n"
     ]
    }
   ],
   "source": [
    "! cd ./71 && go list ./... | xargs -n 1 go test -cover"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "覆盖率阈值（可用于 CI 失败条件）\n",
    "- Go 命令本身没有原生阈值控制，但可以结合 `go tool cover -func` 输出与自定义脚本（或 `awk`、`grep`）判断总覆盖率是否达标。\n",
    "- 根据输出的百分比决定是否退出非零状态码，从而在 CI 中强制覆盖率标准。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\txtest\t\tok  \txtest/until\t0.006s\tcoverage: 100.0% of statements\n"
     ]
    }
   ],
   "source": [
    "! cd ./71 && go test -coverprofile=coverage.out ./..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100.0%\n"
     ]
    }
   ],
   "source": [
    "! cd ./71 && go tool cover -func=coverage.out | grep total | awk '{print $3}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 72 `init()`执行规则及其使用场景"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. **自动执行**：`init()` 无需显式调用，且不能被其他函数直接调用。\n",
    "2. **签名固定**：函数声明必须是 `func init()`，无参数、无返回值。\n",
    "3. **运行时机**：在包内所有变量初始化完成后、`main.main()` 执行前运行。\n",
    "4. **执行顺序**：\n",
    "- 按导入依赖顺序自顶向下递归初始化：被导入包先于导入它的包执行。\n",
    "- 同一包内多个文件的执行顺序，依赖编译器的文件排序（通常按文件名排序），每个文件中变量初始化→`init()`。\n",
    "- 单文件内若存在多个 `init()`，按源代码出现顺序依次执行。\n",
    "5. **执行次数**：每个包的 `init()` 在整个程序生命周期内只执行一次，即使被多个包重复导入。\n",
    "6. **常见用途**：\n",
    "- 注册驱动/插件（例如数据库驱动、HTTP 路由、处理器）。\n",
    "- 初始化全局状态（缓存、日志、配置），但应避免耗时操作。\n",
    "- 校验运行环境（环境变量、必需文件等）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 初始化顺序与执行流程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以观察到：\n",
    "\n",
    "- `alpha` 先于 `beta` 初始化（遵循 import 顺序）。\n",
    "- 变量初始化先于 `init()`。\n",
    "- 同包多个 `init()` 依次执行。\n",
    "- `main.init()` 在所有依赖包完成后执行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "alpha: initValue() during var initialization\n",
      "alpha: first init() from alpha.go\n",
      "alpha: second init() from alpha_extra.go\n",
      "beta: var initializer\n",
      "beta: init()\n",
      "main: init()\n",
      "main: main()\n",
      "alpha.Value = 42\n"
     ]
    }
   ],
   "source": [
    "! cd ./72/01 && go run ."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 匿名导入: `_`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `_ \"<pkg-path>\"` 表示仅触发包的初始化副作用，不直接使用包导出内容。\n",
    "- 常用于驱动、插件、自动注册等场景。\n",
    "- 仍需确保导入顺序正确，因为 `init()` 的执行顺序与依赖关系相关。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 Go 代码里写 `_ \"example.com/initorder/beta\"` 属于 **匿名导入（blank import）**。这句导入语句的目的不是为了直接使用包中导出的标识符，而是为了让该包在程序启动时执行它的 `init()` 函数和全局变量初始化。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为什么需要匿名导入？\n",
    "\n",
    "- `init()` 会在包被导入时自动执行。\n",
    "- 如果单纯为了触发副作用（例如在 `init()` 里注册驱动、路由、插件），而不需要直接调用包里的函数或类型，可以使用匿名导入。\n",
    "- 避免编译器因为“导入了但没使用”而报错。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在我们之前的 `main.go` 示例中：\n",
    "\n",
    "```go\n",
    "import (\n",
    "    \"fmt\"\n",
    "\n",
    "    \"example.com/initorder/alpha\"\n",
    "    _ \"example.com/initorder/beta\"\n",
    ")\n",
    "```\n",
    "\n",
    "- `alpha` 包带名字导入，后续代码会使用 `alpha.Value`。\n",
    "- `beta` 包以 `_` 导入，只是为了让 `beta` 的 `init()` 执行，产生某些副作用（比如打印日志、注册对象），但 `main` 中不直接使用 `beta` 的任何导出标识符。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用场景举例\n",
    "\n",
    "1. **数据库驱动注册**\n",
    "\n",
    "   ```go\n",
    "   import (\n",
    "       \"database/sql\"\n",
    "       _ \"github.com/lib/pq\" // 注册 PostgreSQL 驱动\n",
    "   )\n",
    "   ```\n",
    "\n",
    "   导入后，`pq` 包的 `init()` 会把自己注册到 `database/sql` 的驱动列表中。\n",
    "\n",
    "2. **HTTP 路由初始化**\n",
    "\n",
    "   某些路由包在 `init()` 中自动向全局路由表添加路由。\n",
    "\n",
    "3. **配置/插件自动注册**\n",
    "\n",
    "   通过匿名导入加载模块，让它们在 `init()` 中完成自注册，而无需手动调用。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 用 init() 注册插件（典型用法）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 通过 `init()` 自动注册组件，`main` 调用 `ListProcessors()` 时无需关心具体实现是否提前注册。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Registered processors: [paypal stripe]\n",
      "- paypal demo: Paypal processed $100\n",
      "- stripe demo: Stripe processed $100\n"
     ]
    }
   ],
   "source": [
    "! cd ./72/02 && go run ."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 启动前检查环境变量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Running in fallback mode\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025/09/30 00:17:53 [config] warning: API_KEY is not set; using fallback mode\n"
     ]
    }
   ],
   "source": [
    "! cd ./72/03 && go run ."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "API key loaded: secret\n"
     ]
    }
   ],
   "source": [
    "! cd ./72/03 && API_KEY=secret go run ."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 73 init()一定是最先执行的函数吗"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`init()` 在 Go 中并不是“最先执行的函数”。规则是：\n",
    "\n",
    "1. 每个包在被导入时，会先完成**包级变量初始化**，然后才执行该包的所有 `init()`。\n",
    "2. 多个包之间按 **导入依赖顺序** 初始化：被依赖的包先于依赖它的包完成初始化。\n",
    "3. `main.main()` 最后执行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "包级变量初始化先于 init()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[global initializer] initGlobal() called\n",
      "[init] init() called\n",
      "[main] main() called, global = 42\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import \"fmt\"\n",
    "\n",
    "var global = initGlobal()\n",
    "\n",
    "func initGlobal() int {\n",
    "\tfmt.Println(\"[global initializer] initGlobal() called\")\n",
    "\treturn 42\n",
    "}\n",
    "\n",
    "func init() {\n",
    "\tfmt.Println(\"[init] init() called\")\n",
    "}\n",
    "\n",
    "func main() {\n",
    "\tfmt.Println(\"[main] main() called, global =\", global)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "包初始化顺序与 `init()`\n",
    "- `helper` 包先被初始化（按导入顺序）。\n",
    "- 在每个包内部，依然是“变量初始化 → init()”的顺序。\n",
    "- 等所有依赖包完成后，`main` 包才开始变量初始化、执行 `init()`，最后进入 `main()`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[helper] variable initializer\n",
      "[helper] init()\n",
      "[main] variable initializer\n",
      "[main] init()\n",
      "[main] main()\n",
      "[helper] Work() called\n"
     ]
    }
   ],
   "source": [
    "! cd ./73 && go run ."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 74 go build和go install"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 开发中调试：使用 `go build`（或 `go run`）快速验证逻辑。\n",
    "- 安装工具：使用 `go install module/path@version`，可避免依赖冲突并在系统中统一管理。\n",
    "- 为生产环境构建：视需求选择在构建阶段用 `go build -o` 输出到特定目录，或 `go install` 安装到部署指定路径。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## go build"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**作用**：编译指定的包及其依赖，产出可执行或库文件，但默认不把结果放进模块缓存或 `GOBIN`；一般用于快速验证编译是否通过。\n",
    "\n",
    "**默认输出**：\n",
    "\n",
    "- 若目标是主包（包含 `main` 包），在当前目录生成可执行文件（名称基于目录或 `-o` 参数）；退出后文件仍保留。\n",
    "- 若目标是库包，不生成最终产物，只检查语法和依赖关系。\n",
    "\n",
    "**常见使用场景**：\n",
    "\n",
    "- 开发过程中快速检查工程是否能通过编译。\n",
    "- 编译测试不同平台的产物（结合 `GOOS`、`GOARCH`）。\n",
    "- 临时生成可执行文件供本地运行，不污染 `$GOBIN`。\n",
    "\n",
    "**常用选项**：\n",
    "\n",
    "- `go build ./...` 递归构建当前模块所有包。\n",
    "- `go build -o output-name ./cmd/app` 指定输出文件名/位置。\n",
    "- `GOOS=linux GOARCH=amd64 go build` 交叉编译。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## go install"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**作用**：先编译，再将构建产物安装到特定位置。\n",
    "\n",
    "- 主包（`main`）会安装可执行文件到 `$GOBIN` 或 `$GOPATH/bin`。\n",
    "- 非主包安装为编译缓存对象（在模块模式下，多数场景直接放入构建缓存与编译缓存目录）。\n",
    "\n",
    "**默认行为**：安装完成后不会在当前目录留下可执行文件，而是将结果放到安装目录，方便全局调用。\n",
    "\n",
    "**常见使用场景**：\n",
    "\n",
    "- 安装 CLI 工具或命令行应用供全局使用，如 `go install golang.org/x/tools/cmd/goimports@latest`。\n",
    "- 在 CI/CD 或部署流程中，将项目编译并安装到 `GOBIN` 目录便于后续运行。\n",
    "\n",
    "**Go 1.18+ 特性**：在模块模式下，推荐通过 `go install pkg@version` 方式安装特定版本的命令工具（不会修改当前模块的 `go.mod`）。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Go (gonb)",
   "language": "go",
   "name": "gonb"
  },
  "language_info": {
   "codemirror_mode": "",
   "file_extension": ".go",
   "mimetype": "text/x-go",
   "name": "go",
   "nbconvert_exporter": "",
   "pygments_lexer": "",
   "version": "go1.24.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
